#include "../include/scanner.h"
#include "../include/pool.h"
#include "../include/string.h"
#include "../include/line.h"

bool isChar(unsigned char c)
{
    return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c >= 192);
}

bool isDigit(unsigned char c)
{
    return c >= '0' && c <= '9';
}

_int getChar(Scanner *self, const u_byte *value)
{
    u_byte flags[] = {192, 224, 240};
    for (u_int i = 0; i < 3; i++)
    {
        if (*value < flags[i])
        {
            for (u_int j = 0; j < i + 1; j++)
            {
                Array_Push(&self->char_buffer, *(value + j), _byte);
            }
            return i;
        }
    }
    return 1;
}

typedef enum
{
    STATE_START,
    STATE_INTEGER,
    STATE_B_INTEGER,
    STATE_X_INTEGER,
    STATE_DECIMAL,
    STATE_STRING,
    STATE_WORD,
    STATE_NOTE,
} State;

_int isKeyWord(String *key)
{
    TValue value = Map_Get(&GLOBE.keywords, TSTRING(key));
    if (IS_NULL(value))
        return -1;
    return value.value.integer;
}

void Scanner_Init(Scanner *self)
{
    Array_Init(&self->result, Token);
    Array_Init(&self->char_buffer, u_byte);
    self->line = 1;
    self->spacing = false;
}

Exception *Scanner_Parse_String(Scanner *self, const u_byte *source, Exception *exception)
{
    State state = STATE_START;
    do
    {
        switch (state)
        {
        case STATE_START:
        {
            if (isChar(*source))
            {
                source += getChar(self, source);
                state = STATE_WORD;
            }
            else if (isDigit(*source))
            {
                if (*(source + 1) == 'b' || *(source + 1) == 'B')
                {
                    source++;
                    state = STATE_B_INTEGER;
                }
                else if (*(source + 1) == 'x' || *(source + 1) == 'X')
                {
                    source++;
                    state = STATE_X_INTEGER;
                }
                else if (*source != '0' || *(source + 1) != '0')
                {
                    Array_Push(&self->char_buffer, *source, _byte);
                    state = STATE_INTEGER;
                }
            }
            else if (*source == '\r')
            {
                if (*(source + 1) == '\n')
                {
                    source++;
                }
                self->line++;
            }
            else if (*source == '\n')
            {
                self->line++;
            }
            else
            {
                switch (*source)
                {
                case '+':
                {
                    Token token = {TOKEN_PLUS, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '-':
                {
                    Token token = {TOKEN_MINUS, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '*':
                {
                    Token token = {TOKEN_MUL, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '/':
                {
                    if (*(source + 1) == '/')
                    {
                        state = STATE_NOTE;
                        source++;
                    }
                    else
                    {
                        Token token = {TOKEN_DIV, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                    }
                    break;
                }
                case '%':
                {
                    Token token = {TOKEN_MOD, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '^':
                {
                    Token token = {TOKEN_POWER, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '=':
                {
                    if (*(source + 1) == '=')
                    {
                        Token token = {TOKEN_EQUAL, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                        source++;
                    }
                    else
                    {
                        Token token = {TOKEN_ASSIGN, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                    }
                    break;
                }
                case '<':
                {
                    if (*(source + 1) == '=')
                    {
                        Token token = {TOKEN_LESSEQUAL, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                        source++;
                    }
                    else
                    {
                        Token token = {TOKEN_LESS, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                    }
                    break;
                }
                case '>':
                {
                    if (*(source + 1) == '=')
                    {
                        Token token = {TOKEN_GREATEREQUAL, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                        source++;
                    }
                    else
                    {
                        Token token = {TOKEN_GREATER, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                    }
                    break;
                }
                case '!':
                {
                    Token token = {TOKEN_NOT, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '&':
                {
                    if (*(source + 1) == '&')
                    {
                        Token token = {TOKEN_AND, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                        source++;
                    }
                    else
                    {
                        Token token = {TOKEN_ANDBIT, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                    }
                    break;
                }
                case '|':
                {
                    if (*(source + 1) == '|')
                    {
                        Token token = {TOKEN_OR, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                        source++;
                    }
                    else
                    {
                        Token token = {TOKEN_ORBIT, self->line, TNULL};
                        Array_Push(&self->result, token, Token);
                    }
                    break;
                }
                case ';':
                {
                    Token token = {TOKEN_SEMICOLON, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case ',':
                {
                    Token token = {TOKEN_COMMA, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case ':':
                {
                    Token token = {TOKEN_COLON, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '.':
                {
                    Token token = {TOKEN_POINT, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '(':
                {
                    Token token = {TOKEN_LEFTPARENTHESIS, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case ')':
                {
                    Token token = {TOKEN_RIGHTPARENTHESIS, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '{':
                {
                    Token token = {TOKEN_LEFTBRACE, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '}':
                {
                    Token token = {TOKEN_RIGHTBRACE, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '[':
                {
                    Token token = {TOKEN_LEFTBRACKET, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case ']':
                {
                    Token token = {TOKEN_RIGHTBRACKET, self->line, TNULL};
                    Array_Push(&self->result, token, Token);
                    break;
                }
                case '"':
                {
                    state = STATE_STRING;
                    break;
                }
                case ' ':
                {
                    break;
                }
                default:
                {
                    if (*source != '\0')
                        printf("Unknown character: %c\n", *source);
                    break;
                }
                }
            }
            break;
        }
        case STATE_INTEGER:
        {
            if (isDigit(*source))
            {
                Array_Push(&self->char_buffer, *source, _byte);
            }
            else if (*source == '.')
            {
                Array_Push(&self->char_buffer, *source, _byte);
                state = STATE_DECIMAL;
            }
            else
            {
                Array_Push(&self->char_buffer, '\0', _byte);
                Token token = {TOKEN_INTEGER, self->line, TINTEGER(atoi(self->char_buffer.pointer))};
                Array_Clear(&self->char_buffer);
                Array_Push(&self->result, token, Token);
                state = STATE_START;
                source--;
            }
            break;
        }
        case STATE_B_INTEGER:
        {
            if (*source == '0' || *source == '1')
            {
                Array_Push(&self->char_buffer, *source, _byte);
            }
            else
            {
                Array_Push(&self->char_buffer, '\0', _byte);
                _long result = 0;
                _byte *b = (_byte *)self->char_buffer.pointer;
                while (*b != '\0')
                {
                    result = result << 1;
                    result = result | (*b - '0');
                    b++;
                }
                Token token = {TOKEN_INTEGER, self->line, TINTEGER(result)};
                Array_Clear(&self->char_buffer);
                Array_Push(&self->result, token, Token);
                state = STATE_START;
                source--;
            }
            break;
        }
        case STATE_X_INTEGER:
        {
            if ((*source >= '0' && *source <= '9') || (*source >= 'a' && *source <= 'f'))
            {
                Array_Push(&self->char_buffer, *source, _byte);
            }
            else if (*source >= 'A' && *source <= 'F')
            {
                Array_Push(&self->char_buffer, *source + 32, _byte);
            }
            else
            {
                Array_Push(&self->char_buffer, '\0', _byte);

                _long result = 0;
                _byte *b = (_byte *)self->char_buffer.pointer;
                while (*b != '\0')
                {
                    result = result << 4;
                    result = result | (*b >= 'a' ? *b - 'a' + 10 : *b - '0');
                    b++;
                }

                Token token = {TOKEN_INTEGER, self->line, TINTEGER(result)};
                Array_Clear(&self->char_buffer);
                Array_Push(&self->result, token, Token);
                state = STATE_START;
                source--;
            }
            break;
        }
        case STATE_DECIMAL:
        {
            if (isDigit(*source))
            {
                Array_Push(&self->char_buffer, *source, _byte);
            }
            else
            {
                Array_Push(&self->char_buffer, '\0', _byte);
                Token token = {TOKEN_DECIMAL, self->line, TDECIMAL(atof(self->char_buffer.pointer))};
                Array_Clear(&self->char_buffer);
                Array_Push(&self->result, token, Token);
                state = STATE_START;
                source--;
            }
            break;
        }
        case STATE_STRING:
        {
            while (*source != '"')
            {
                if (*source == '\\')
                {
                    source++;
                    switch (*source)
                    {
                    case 'n':
                    {
                        Array_Push(&self->char_buffer, '\n', _byte);
                        break;
                    }
                    case 't':
                    {
                        Array_Push(&self->char_buffer, '\t', _byte);
                        break;
                    }
                    case '\\':
                    {
                        Array_Push(&self->char_buffer, '\\', _byte);
                        break;
                    }
                    case '"':
                    {
                        Array_Push(&self->char_buffer, '"', _byte);
                        break;
                    }
                    case '\n':
                    {
                        self->line++;
                        break;
                    }
                    }
                }
                else if (*source == '\n' || *source == '\r')
                {
                    return;
                }
                else
                {
                    Array_Push(&self->char_buffer, *source, _byte);
                }
                source++;
            }
            source++;
            Array_Push(&self->char_buffer, '\0', _byte);
            Token token = (Token){TOKEN_STRING, self->line, TSTRING(GLOBE_CREATE_STRING(self->char_buffer.pointer))};
            Array_Push(&self->result, token, Token);
            Array_Clear(&self->char_buffer);
            state = STATE_START;
            break;
        }
        case STATE_WORD:
        {
            if (isChar(*source) || isDigit(*source))
            {
                source += getChar(self, source);
            }
            else
            {
                Array_Push(&self->char_buffer, '\0', _byte);
                Token token;

                String *key = GLOBE_CREATE_STRING(self->char_buffer.pointer);
                _int result = isKeyWord(key);
                if (result != -1)
                {
                    token = (Token){result, self->line, TNULL};
                }
                else
                {
                    token = (Token){TOKEN_ID, self->line, TSTRING(key)};
                }
                Array_Push(&self->result, token, Token);
                Array_Clear(&self->char_buffer);
                state = STATE_START;
                source--;
            }
            break;
        }
        case STATE_NOTE:
        {
            while (true)
            {
                if (*source == '\r' || *source == '\n')
                {
                    source++;
                    state = STATE_START;
                    break;
                }
                else if (*source == '\0')
                {
                    state = STATE_START;
                    break;
                }
            }
            break;
        }
        }
    } while (*(source++) != '\0');
    Token semicolon = {TOKEN_SEMICOLON, self->line, TNULL};
    Array_Push(&self->result, semicolon, Token);
    Token eof = {TOKEN_EOF, self->line, TNULL};
    Array_Push(&self->result, eof, Token);
}

Exception *Scanner_Parse_File(Scanner *self, const u_byte *path, Exception *exception)
{
    FILE *file = NULL;
    file = fopen(path, "rb");
    fseek(file, 0, SEEK_END);
    u_int length = ftell(file) + 1;
    fseek(file, 0, SEEK_SET);
    _byte *value = Globe_Malloc(sizeof(_byte) * length);
    fread(value, sizeof(_byte), length, file);
    value[length - 1] = '\0';
    Scanner_Parse_String(self, value, exception);
    fclose(file);
    Globe_Free(value);
    return exception;
}

void Scanner_Print(Scanner *scanner)
{
    for (u_int i = 0; i < scanner->result.length; i++)
    {
        Token token = Array_Get(&scanner->result, i, Token);
        printf("[%d]\t", token.line);
        printf("%s  ", TOKEN_CATEGORY_STRING[token.category]);
        if (token.category == TOKEN_STRING || token.category == TOKEN_ID)
        {
            printf("[\"%s\"]::0x%x", ((String *)token.value.value.object)->array, token.value.value.object);
        }
        else if (token.value.category == T_INTEGER)
        {
            printf("[\"%d\"]", token.value.value);
        }
        else if (token.value.category == T_DECIMAL)
        {
            printf("[\"%lf\"]", token.value.value);
        }

        printf("\n");
    }
}